/*
 * Copyright 2011 Katsunori Koyanagi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package sweezy.swf.tools.abcdump
{
	import sweezy.swf.abc.structure.ClassInfo;
	import sweezy.swf.abc.structure.ClassTrait;
	import sweezy.swf.abc.structure.ConstantPoolInfo;
	import sweezy.swf.abc.structure.InstanceInfo;
	import sweezy.swf.abc.structure.NamespaceInfo;
	import sweezy.swf.abc.structure.QNameInfo;
	import sweezy.swf.abc.structure.TraitInfo;
	import sweezy.swf.constant.InstanceInfoFlags;
	import sweezy.swf.constant.NamespaceKind;
	import sweezy.swf.constant.TraitAttributes;

	internal class ClassDef extends ABCElementDef
	{

		private var _classInfo:ClassInfo;

		private var _classTrait:ClassTrait;

		private var _instanceInfo:InstanceInfo;

		public function ClassDef(container:ABCcontainerDef, classTrait:ClassTrait)
		{
			super(container);

			_classTrait = classTrait;
			_classInfo = _abc.classInfos[_classTrait.classIndex];
			_instanceInfo = _abc.instanceInfos[_classTrait.classIndex];
		}

		override public function dump(stream:IDumpStream):void
		{
			var variableDef:VariableDef;
			var functionDef:FunctionDef;
			var values:Array;
			var result:String = "";
			var isInterface:Boolean = (_instanceInfo.flags & InstanceInfoFlags.CLASS_INTERFACE) === InstanceInfoFlags.CLASS_INTERFACE;
			var ns:Namespace = namespace;
			var n:String;

			dumpMetadata(stream);

			if (ns !== null && ns !== Utils.PRIVATE)
			{
				result += ns;
				result += " ";
			}
			if ((_instanceInfo.flags & InstanceInfoFlags.CLASS_SEALED) !== InstanceInfoFlags.CLASS_SEALED)
			{
				result += "dynamic ";
			}
			if ((_instanceInfo.flags & InstanceInfoFlags.CLASS_FINAL) === InstanceInfoFlags.CLASS_FINAL)
			{
				result += "final ";
			}
			if (isInterface)
			{
				result += "interface ";
			}
			else
			{
				result += "class ";
			}

			result += simpleName;

			if (!isInterface)
			{
				n = superClassName;
				if (n)
				{
					result += " extends ";
					result += n;
				}
			}

			var ifs:Array = interfaceNames;
			if (ifs.length > 0)
			{
				result += isInterface ? " extends " : " implements ";
				result += ifs.join(", ");
			}
			stream.println(result);

			stream.println("{");
			stream.println();
			stream.indentLevel++;

			stream.printSection("static initializer");
			new FunctionDef(_container, this, _classInfo, true).dump(stream);
			stream.println();

			values = getVariableDefs(false);
			if (values.length > 0)
			{
				stream.printSection("static variables");
				for each (variableDef in values)
				{
					variableDef.dump(stream);
					stream.println();
				}
			}

			values = getVariableDefs(true);
			if (values.length > 0)
			{
				stream.printSection("instance variables");
				for each (variableDef in values)
				{
					variableDef.dump(stream);
					stream.println();
				}
			}

			values = getMethodFunctionDefs(false);
			if (values.length > 0)
			{
				stream.printSection("static methods");
				for each (functionDef in values)
				{
					functionDef.dump(stream);
					stream.println();
				}
			}

			stream.printSection("constructor");
			new FunctionDef(_container, this, _instanceInfo, false).dump(stream);
			stream.println();

			values = getMethodFunctionDefs(true);
			if (values.length > 0)
			{
				stream.printSection("instance methods");
				for each (functionDef in values)
				{
					functionDef.dump(stream);
					stream.println();
				}
			}

			stream.indentLevel--;
			stream.println("}");
		}

		public function get name():String
		{
			return Utils.multinameToString(_constantPool, _classTrait.name);
		}

		public function get namespace():Namespace
		{
			var cp:ConstantPoolInfo = _constantPool;
			var name:uint = _instanceInfo.name;

			var qname:QNameInfo = cp.multinameInfos[name] as QNameInfo;
			if (qname === null)
			{
				return null;
			}

			var namespace:NamespaceInfo = cp.namespaceInfos[qname.ns] as NamespaceInfo;
			if (namespace === null)
			{
				return null;
			}

			switch (namespace.kind)
			{
				case NamespaceKind.PACKAGE_INTERNAL_NS:
					return Utils.INTERNAL;
				case NamespaceKind.PRIVATE_NS:
					return Utils.PRIVATE;
				case NamespaceKind.PACKAGE_NAMESPACE:
					return Utils.PUBLIC;
			}

			return null;
		}

		public function get simpleName():String
		{
			var qName:QNameInfo = _constantPool.multinameInfos[_instanceInfo.name] as QNameInfo;
			if (qName === null)
			{
				var n:String = name;
				var i:int = name.indexOf("::");
				if (i > -1)
				{
					n = n.substring(i + 2);
				}

				return n;
			}

			return _constantPool.stringConstants[qName.name];
		}

		private function dumpMetadata(stream:IDumpStream):void
		{
			if ((_classTrait.attributes & TraitAttributes.METADATA) === TraitAttributes.METADATA)
			{
				for each (var index:uint in _classTrait.metadata)
				{
					new MetadataDef(_container, _abc.metadataInfos[index]).dump(stream);
				}
			}
		}

		private function getMethodFunctionDefs(instance:Boolean):Array
		{
			var methodFunctionDefs:Array = [];
			var traits:Array = instance ? _instanceInfo.traitInfos : _classInfo.traitInfos;

			for each (var trait:TraitInfo in traits)
			{
				if (Utils.isMethod(trait))
				{
					methodFunctionDefs[methodFunctionDefs.length] = new FunctionDef(_container, this, trait, !instance);
				}
			}

			return methodFunctionDefs;
		}

		private function getVariableDefs(instance:Boolean):Array
		{
			var variableDefs:Array = [];
			var traits:Array = instance ? _instanceInfo.traitInfos : _classInfo.traitInfos;

			for each (var trait:TraitInfo in traits)
			{
				if (Utils.isVariable(trait))
				{
					variableDefs[variableDefs.length] = new VariableDef(_container, trait, !instance);
				}
			}

			return variableDefs;
		}

		private function get interfaceNames():Array
		{
			var result:Array = [];
			for each (var name:uint in _instanceInfo.interfaces)
			{
				result[result.length] = Utils.multinameToString(_constantPool, name);
			}

			return result;
		}

		private function get superClassName():String
		{
			return _instanceInfo.superName === 0 ? "" : Utils.multinameToString(_constantPool, _instanceInfo.superName);
		}
	}
}
